﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LightCAD.MathLib
{

    /// <summary>
    /// 拉伸体
    /// </summary>
    public class Extrude3d : Solid3d
    {
        private PlanarSurface3d profile;
        private double depth;
        private int curveSegments;
        public Surface3d[] Surfaces;
        private TopoFaceModel topoFaceModel;
        private double topScale = 1;
        private double bottomScale = 1;
        private Vector3 normal = new Vector3(0, 0, 1);
        public Vector3 Origin { get; set; } = new Vector3();
        public Vector3 Normal { get => normal; set => SetSize(profile,value, depth, topScale, bottomScale, curveSegments); }  
        public PlanarSurface3d Profile { get => profile; set => SetSize(value, normal, depth, topScale, bottomScale, curveSegments); }
        public double Depth { get => depth; set => SetSize(profile, normal, value, topScale, bottomScale, curveSegments); }
        public int CurveSegments { get => curveSegments; set => SetSize(profile, normal, depth, topScale, bottomScale, value); }
        public double TopScale { get => topScale; set => SetSize(profile, normal, depth, value, bottomScale, curveSegments); }
        public double BottomScale { get => bottomScale; set => SetSize(profile, normal, depth, topScale, value, curveSegments); }

        public Extrude3d() { }
        public Extrude3d(PlanarSurface3d profile,Vector3 normal ,double depth = 100, double topScale = 1, double bottomScale = 1, int curveSegments =12)
        {
            this.SetSize( profile, normal,  depth , topScale,bottomScale, curveSegments);
        }
        public void SetSize(PlanarSurface3d profile, Vector3 normal ,double depth = 100,double topScale=1, double bottomScale = 1, int curveSegments = 12)
        {
            if (topoFaceModel != null && (this.profile != profile || this.normal != normal || this.depth != depth || this.topScale != topScale || this.bottomScale != bottomScale ||  this.curveSegments != curveSegments  ))
            {
                topoFaceModel = null;
            }
            this.normal = normal; 
            this.profile= profile;
            this.depth = depth;
            this.topScale = topScale;
            this.bottomScale= bottomScale;
            this.curveSegments = curveSegments;
        }
        public override TopoFaceModel CreateTopoModel()
        {
            if (topoFaceModel != null)
                return topoFaceModel;
            var topOrign = Origin.Clone().AddScaledVector(Normal.Normalize(), depth);
            var topLoops = profile.OuterLoop.Select(n => {
                var curve = n.Clone();
                if (curve is Line3d line)
                {
                    line.Start.MulScalar(topScale);
                    line.End.MulScalar(topScale);
                    line.Translate(topOrign);
                }
                else if (curve is Arc3d arc)
                {
                    arc.Center.MulScalar(topScale);
                    arc.Radius *= topScale;
                    arc.Translate(topOrign);
                }
                return curve;
            }
            ).ToList();
            var bottomLoops = profile.OuterLoop.Select(n=> {
                var curve = n.Clone();
                if (curve is Line3d line)
                {
                    line.Start.MulScalar(bottomScale);
                    line.End.MulScalar(bottomScale);
                    line.Translate(Origin);
                }
                else if (curve is Arc3d arc)
                {
                    arc.Center.MulScalar(bottomScale);
                    arc.Radius *= bottomScale;
                    arc.Translate(Origin);
                }
                return curve;
            }
            ).ToList();
            //profile.OuterLoop.Select(n => n.Translate(profile.Normal.Clone().Normalize().MulScalar(depth))).ToList();
            var bottomFace = new PlanarSurface3d(new Plane(profile.Normal.Clone().Negate()), bottomLoops);
            bottomFace.InnerLoops = profile.InnerLoops.Clone().Select(m => m.Select(n => {
                {
                    var curve = n.Clone();
                    if (curve is Line3d line)
                    {
                        line.Start.MulScalar(bottomScale);
                        line.End.MulScalar(bottomScale);
                        line.Translate(Origin);
                    }
                    else if (curve is Arc3d arc)
                    {
                        arc.Center.MulScalar(bottomScale);
                        arc.Radius *= bottomScale;
                        arc.Translate(Origin);
                    }
                    return curve;
                }
            }).ToList()).ToList();
            //profile.InnerLoops.Select(m=>  m = m.Select(n => n.Translate(profile.Normal.Clone().Normalize().MulScalar(depth))).ToList()).ToList();

            var topFace = new PlanarSurface3d(new Plane(profile.Normal.Clone()), topLoops);
            topFace.InnerLoops = profile.InnerLoops.Clone().Select(m => m.Select(n => {
                {
                    var curve = n.Clone();
                    if (curve is Line3d line)
                    {
                        line.Start.MulScalar(topScale);
                        line.End.MulScalar(topScale);
                        line.Translate(topOrign);
                    }
                    else if (curve is Arc3d arc)
                    {
                        arc.Center.MulScalar(topScale);
                        arc.Radius *= topScale;
                        arc.Translate(topOrign);
                    }
                    return curve;
                }
            }).ToList()).ToList();
            var aroundFaces = new List<Surface3d>();
            aroundFaces.Add(topFace);
            aroundFaces.Add(bottomFace);
            for(var i= 0;i < profile.OuterLoop.Count;i++)
            {
                var curve = topFace.OuterLoop[i].Clone();
                var btmCurve = bottomFace.OuterLoop[i].Clone();
                var surface = GenerateSurface(curve, btmCurve);
                aroundFaces.Add(surface);
            }
            for (var i = 0; i < profile.InnerLoops.Count; i++)
            {
                for (var k = 0; k < profile.InnerLoops[i].Count; k++)
                {
                    var curve = topFace.InnerLoops[i][k];
                    var btmCurve = bottomFace.InnerLoops[i][k];
                    var surface = GenerateSurface(curve, btmCurve);
                    aroundFaces.Add(surface);
                }
            }
            this.Surfaces = aroundFaces.ToArray();
            this.topoFaceModel = new TopoFaceModel() { Surfaces = this.Surfaces.ToListEx() };
            return this.topoFaceModel;
        }
        private Surface3d GenerateSurface(Curve3d top, Curve3d curve)
        {
            if (curve is Arc3d arc)
            {
                var topArc = top as Arc3d;
                //var origin = arc.Center.Clone().AddScaledVector(profile.Normal.Clone().Normalize().Negate(),depth);
                var origin = arc.Center.Clone();
                var normal = topArc.Center.Clone().Sub(arc.Center).Normalize();
                return new CylindricalSurface3d(topArc.Radius, arc.Radius, arc.Center.DistanceTo(topArc.Center), arc.IsClockwise ? arc.EndAngle : arc.StartAngle, arc.IsClockwise ? arc.StartAngle : arc.EndAngle, origin, normal, 32, arc.IsClockwise);
            }
            if (curve is Line3d)
            {
                var topLine = top as Line3d;
                var bottomLine = curve.Clone().Reverse() as Line3d;
                var xAxis = topLine.End.Clone().Sub(topLine.Start).Normalize();
                var yAxis = topLine.Start.Clone().Sub(bottomLine.End).Cross(xAxis).Normalize().Negate();
                var leftLine = new Line3d(topLine.End.Clone(), bottomLine.Start.Clone());
                var rightLine = new Line3d(bottomLine.End.Clone(), topLine.Start.Clone());
                return new PlanarSurface3d(new Plane(yAxis), new List<Curve3d>() { topLine.Clone(), leftLine, bottomLine.Clone(), rightLine });
            }
            return null;
        }
        public override Solid3d Mesh()
        {
            base.Mesh();

            var idxsCount = this.Geometry.Indics.Length;
            //this.Geometry.Groups = new GeometryGroup[3]
            //{
            //    new GeometryGroup{ Name = "Top", Start = 0, Count = topVertCount , MaterialIndex = 0 },
            //    new GeometryGroup{ Name = "Bottom", Start = topVertCount, Count = topVertCount , MaterialIndex = 1},
            //    new GeometryGroup{ Name = "Around", Start = topVertCount*2, Count = idxsCount- topVertCount*2, MaterialIndex = 2 }, 
            //};
            this.Geometry.Groups[0].MaterialIndex = 1;
            this.Geometry.Groups[1].MaterialIndex = 2;
            
            this.Edge = new GeometryData();       
            var verteice = new List<Vector3>();
            var idxArray = new ListEx<int>() { };
            for (var i = 0; i < profile.OuterLoop.Count; i++)
            {
                var curve = this.Surfaces[0].OuterLoop[i].Clone();
                var btmCurve = this.Surfaces[1].OuterLoop[i].Clone();
                var count = verteice.Count;
                if(curve is Line3d line)
                {
                    verteice.Add(curve.Start);
                    verteice.Add(btmCurve.Start);
                    idxArray.Push(count, count + 1);
                    idxArray.Push(count, count + 2);
                    idxArray.Push(count + 1, count + 3);
                }
                else if (curve is Arc3d arc)
                {
                    var tps = arc.GetPoints(32);
                    var bts = btmCurve.GetPoints(32);
                    for (var k=0;k<31;k++)
                    {
                        verteice.Add(tps[k]);
                        verteice.Add(bts[k]);
                        idxArray.Push(count, count + 1); 
                        idxArray.Push(count, count + 2);
                        idxArray.Push(count + 1, count + 3);
                    }
                }
                if (i== profile.OuterLoop.Count-1)
                {
                    verteice.Add(curve.End.Clone());
                    verteice.Add(btmCurve.End.Clone());
                }
            } 
            for (var i = 0; i < profile.InnerLoops.Count; i++)
            {
                for (var k = 0; k < profile.InnerLoops[i].Count; k++)
                {
                    var curve = this.Surfaces[0].InnerLoops[i][k];
                    var btmCurve = this.Surfaces[1].InnerLoops[i][k];
                    var count = verteice.Count;
                    if (curve is Line3d line)
                    {
                        verteice.Add(curve.Start);
                        verteice.Add(btmCurve.Start);
                        idxArray.Push(count, count + 1);
                        idxArray.Push(count, count + 2);
                        idxArray.Push(count + 1, count + 3);
                    }
                    else if (curve is Arc3d arc)
                    {
                        var tps = arc.GetPoints(32);
                        var bts = btmCurve.GetPoints(32);
                        for (var j = 0; j < 31; j++)
                        {
                            verteice.Add(tps[j]);
                            verteice.Add(bts[j]);
                            idxArray.Push(count, count + 1);
                            idxArray.Push(count, count + 2);
                            idxArray.Push(count + 1, count + 3);
                        }
                    }
                    if (k == profile.InnerLoops[i].Count - 1)
                    {
                        verteice.Add(curve.End.Clone());
                        verteice.Add(btmCurve.End.Clone());
                    }
                }
            }

            this.Edge.Verteics = Utils.GenerateVertexArr(verteice);
            this.Edge.Indics = idxArray.ToArray();
            return this;
        }
    }
}
